/*	ObservationData.cpp
	Copyright 2012 Nico Marrero
	This file is part of SCRNOBS.

    SCRNOBS is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    SCRNOBS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with SCRNOBS.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ObservationData.h"
#include <wx/regex.h>
#include <wx/arrimpl.cpp>
#include <cctype>

wxXmlNode *CreateXmlContentNode(wxString Name, wxString Content)
{
	wxXmlNode *Element = new wxXmlNode(wxXML_ELEMENT_NODE,Name);
	Element->AddChild(new wxXmlNode(wxXML_TEXT_NODE,Content,Content));
	return(Element);
}
#define ADD_XML_FORMATTED_STRING_NODE(parent, name) parent->AddChild(CreateXmlContentNode(#name,this->GetFormatted##name##()));
#define ADD_XML_STRING_NODE(parent, name) parent->AddChild(CreateXmlContentNode(#name,this->Get##name##()));
#define ADD_XML_ARRAY_NODE(parent, array, name) parent->AddChild(CreateXmlContentNode(#name,array[(int)this->Get##name##()]));
#define ADD_XML_FLOAT_NODE(parent, name) parent->AddChild(CreateXmlContentNode(#name,floatTowxString(this->Get##name##())));
#define ADD_XML_INT_NODE(parent, name) parent->AddChild(CreateXmlContentNode(#name,intTowxString(this->Get##name##())));

#define CHECK_XML_STRING_NODE(node, name) if (node->GetName() == #name){this->Set##name(node->GetNodeContent());}
#define CHECK_XML_FLOAT_NODE(node, name) if (node->GetName() == #name){double val; node->GetNodeContent().ToDouble(&val); this->Set##name((float)val);}
#define CHECK_XML_INT_NODE(node, name) if (node->GetName() == #name){long val; node->GetNodeContent().ToLong(&val); this->Set##name((int)val);}
#define CHECK_XML_ARRAY_NODE(node, type, array, size, name) if (node->GetName() == #name){for (unsigned int i = 0; i < size; ++i){if (array[i] == node->GetNodeContent()){this->Set##name((type)i); break;}}}

#define WRITE_CSV_FORMATTED_STRING(strm, name) strm << "\"" << this->GetFormatted##name() << "\",";
#define WRITE_CSV_STRING(strm, name) strm << "\"" << this->Get##name() << "\",";
#define WRITE_CSV_FLOAT(strm, name) strm << this->Get##name() << ",";
#define WRITE_CSV_INT(strm, name) strm << this->Get##name() << ",";
#define WRITE_CSV_ARRAY(strm, array, name) strm << "\"" << array[this->Get##name()] << "\",";

wxString floatTowxString(float input){return wxString::Format("%.4f",input);}
float wxStringTofloat(wxString input){double val; input.ToDouble(&val); return((float)val);}
DECLARE_EXPOSURE_TEXT_TYPE(float,floatTowxString,wxStringTofloat)

wxString intTowxString(int input){return wxString::Format("%d",input);}
int wxStringToint(wxString input){long val; input.ToLong(&val); return((int)val);}
DECLARE_EXPOSURE_TEXT_TYPE(int,intTowxString,wxStringToint)

wxString TimezoneStr[] = {"EST","EDT"};
wxString DayOfWeekStr[] = {"MON","TUE","WED","THU","FRI","SAT","SUN"};

ObservationData::ObservationData():m_Date(),m_Time(),m_DayOfWeek(MON),m_Timezone(EDT),m_Observer(),m_Temp(0.0),
	m_DewPoint(0.0),m_PresChg3hr(0.0),m_StationPres(0.0),m_SeaLvlPres(0.0),m_WindSpeed(0.0),
	m_WindDir(0.0),m_CloudCover(0),m_CloudType(),m_MaxTemp_8to8(0.0),m_MaxTemp_12to12(0.0),
	m_MinTemp_8to8(0.0),m_MinTemp_12to12(0.0),m_HDD_8to8(0.0),m_HDD_12to12(0.0),m_SolarRad(0.0),
	m_Precip(0.0),m_SnowFall(0.0),m_SnowDepth(0.0),m_AvgWindSpeed(0.0)
{
	ObservationData::sm_data[ObservationData::sm_data.size()] = this;
}
ObservationData::~ObservationData(){}

void ObservationData::SetDataChanged(void)
{
	ObservationData::sm_changed = true;
}
bool ObservationData::GetDataChanged(void)
{
	static unsigned int size = ObservationData::sm_data.size();
	if (size != ObservationData::sm_data.size())
	{
		size = ObservationData::sm_data.size(); ObservationData::sm_changed = false; return(true);
	}
	else if (ObservationData::sm_changed == true)
	{
		ObservationData::sm_changed = false; return(true);
	}
	return(false);
}
unsigned int ObservationData::GetDataCount(void){return ObservationData::sm_data.size();}
wxString* ObservationData::GetDataIDs(void)
{
	wxString *Result = new wxString[ObservationData::sm_data.size()];
	unsigned int counter = 0;
	for(ObservationDataIDHash::iterator it = ObservationData::sm_data.begin(), end = ObservationData::sm_data.end(); it != end; ++it, ++counter)
	{
		Result[counter] = it->second->GetDataID();
	}
	return(Result);
}
int* ObservationData::GetDataIndices(void)
{
	int *Result = new int[ObservationData::sm_data.size()];
	unsigned int counter = 0;
	for(ObservationDataIDHash::iterator it = ObservationData::sm_data.begin(), end = ObservationData::sm_data.end(); it != end; ++it, ++counter)
	{
		Result[counter] = it->first;
	}
	return(Result);
}
int* ObservationData::GetSortedDataIndices(void)
{
	size_t ListSize = ObservationData::sm_data.size();
	int *Result = new int[ListSize];
	wxString *IDs = new wxString[ListSize];
	unsigned int counter = 0;
	for(ObservationDataIDHash::iterator it = ObservationData::sm_data.begin(), end = ObservationData::sm_data.end(); it != end; ++it, ++counter)
	{
		Result[counter] = it->first;
		IDs[counter] = it->second->GetDataID();
	}
	
	if (ListSize > 1)
	{
		wxString TempStr;
		int TempIndex;
		for(unsigned int index1 = 0, size = ListSize-1; index1 < size; ++index1)
		{
			for(unsigned int index2 = index1+1, size = ListSize; index2 < size; ++index2)
			{
				int cmp = ExposureString(IDs[index1]).Cmp(IDs[index2]);
				if (cmp > 0)
				{
					TempStr = IDs[index2]; IDs[index2] = IDs[index1]; IDs[index1] = TempStr;
					TempIndex = Result[index2]; Result[index2] = Result[index1]; Result[index1] = TempIndex;
				}
			}
		}
	}
	delete [] IDs;
	return(Result);
}
ObservationData *ObservationData::FindDataByID(wxString DataID)
{
	for(ObservationDataIDHash::iterator it = ObservationData::sm_data.begin(), end = ObservationData::sm_data.end(); it != end; ++it)
	{
		if (it->second->GetDataID() == DataID)
		{
			return it->second;
		}
	}
	return(NULL);
}
ObservationData *ObservationData::FindDataByIndex(unsigned int index)
{
	if (index < ObservationData::sm_data.size()){return(ObservationData::sm_data[index]);}
	return(NULL);
}

int ObservationData::FindIndex(ObservationData* data)
{
	for(ObservationDataIDHash::iterator it = ObservationData::sm_data.begin(), end = ObservationData::sm_data.end(); it != end; ++it)
	{
		if (it->second == data){return(it->first);}
	}
	return(wxNOT_FOUND);
}

void ObservationData::SaveAllDataXML(wxXmlNode *Root)
{
	wxProgressDialog progress("Saving...","Building XML...",ObservationData::sm_data.size(),wxTheApp->GetTopWindow());
	int *Indices = ObservationData::GetSortedDataIndices();
	for(unsigned int counter = 0,size = ObservationData::sm_data.size(); counter < size; ++counter)
	{
		progress.Update(counter);
		Root->AddChild(ObservationData::sm_data[Indices[counter]]->SaveXML());
	}
	delete [] Indices;
}
bool ObservationData::LoadAllDataXML(wxXmlNode* Root)
{
	if (!Root || Root->GetName() != "ObservationData"){return(false);}
	bool Result = true;
	wxXmlNode *CurrentNode = Root;
	ObservationData *NewData = NULL;
	//Get number of nodes
	unsigned int counter = 1;
	while(CurrentNode = CurrentNode->GetNext()){counter++;}
	CurrentNode = Root;
	wxProgressDialog progress("Loading...","Reading XML data...",counter,wxTheApp->GetTopWindow());
	counter = 0;
	while(CurrentNode)
	{
		progress.Update(counter++);
		NewData = new ObservationData();
		NewData->LoadXML(CurrentNode);
		NewData = NULL;

		CurrentNode = CurrentNode->GetNext();
	}
	return(Result);
}
wxTextOutputStream& ObservationData::ExportAllDataCSV(wxTextOutputStream& strm)
{
	wxProgressDialog progress("Exporting...","Building CSV...",ObservationData::sm_data.size(),wxTheApp->GetTopWindow());
	strm << "Date" << ", ";
	strm << "Time" << ", ";
	strm << "DayOfWeek" << ", ";
	strm << "Timezone" << ", ";
	strm << "Observer" << ", ";

	strm << "Temp" << ", ";
	strm << "DewPoint" << ", ";
	strm << "PresChg3hr" << ", ";
	strm << "StationPres" << ", ";
	strm << "SeaLvlPres" << ", ";
	strm << "WindSpeed" << ", ";
	strm << "WindDir" << ", ";
	strm << "CloudCover" << ", ";
	strm << "CloudType" << ", ";

	strm << "MaxTemp_8to8" << ", ";
	strm << "MaxTemp_12to12" << ", ";
	strm << "MinTemp_8to8" << ", ";
	strm << "MinTemp_12to12" << ", ";
	strm << "HDD_8to8" << ", ";
	strm << "HDD_12to12" << ", ";
	strm << "SolarRad" << ", ";
	strm << "Precip" << ", ";
	strm << "SnowFall" << ", ";
	strm << "SnowDepth" << ", ";
	strm << "AvgWindSpeed" << ", ";

	strm << "Notes" << "\n";
	int *Indices = ObservationData::GetSortedDataIndices();
	for(unsigned int counter = 0,size = ObservationData::sm_data.size(); counter < size; ++counter)
	{
		progress.Update(counter);
		ObservationData::sm_data[Indices[counter]]->ExportCSV(strm) << "\n";
	}
	delete [] Indices;
	return(strm);
}
wxTextOutputStream& ObservationData::ExportAllDataSCRNOBS(wxTextOutputStream& strm)
{
	wxProgressDialog progress("Exporting...","Building SCRNOBS...",ObservationData::sm_data.size(),wxTheApp->GetTopWindow());
	int *Indices = ObservationData::GetSortedDataIndices();
	for(unsigned int counter = 0,size = ObservationData::sm_data.size(); counter < size; ++counter)
	{
		progress.Update(counter);
		ObservationData::sm_data[Indices[counter]]->ExportSCRNOBS(strm) << "\n";
	}
	delete [] Indices;
	return(strm);
}
wxString ObservationData::GetDataID(){return(this->GetDate() + " " + this->GetTime() + " " + this->GetObserver());}
wxString ObservationData::GetFormattedDate()
{
	wxDateTime date;
	if (!date.ParseDate(this->GetDate())){return(this->GetDate());}
	return date.Format("%m/%d/%y");
	//return date.FormatDate();
}
wxString ObservationData::GetSCRNOBSFormattedDate()
{
	wxDateTime date;
	if (!date.ParseDate(this->GetDate())){return(this->GetDate());}
	return date.Format("%y%2m%2d");
}
wxString ObservationData::GetFormattedTime()
{
	wxDateTime time;
	if (!time.ParseTime(this->GetTime()))
	{
		wxString TimeStr = this->GetTime();
		wxString NewStr = ((TimeStr.Len()<4)?"0":"") + TimeStr.Left(TimeStr.Len() - 2) + ":" + TimeStr.Right(2);
		if (!time.ParseTime(NewStr))
		{
			return(TimeStr);
		}
	}
	//return time.Format("%H%M");
	return time.FormatTime();
}
wxString ObservationData::GetSCRNOBSFormattedTime()
{
	wxDateTime time;
	if (!time.ParseTime(this->GetTime()))
	{
		wxString TimeStr = this->GetTime();
		wxString NewStr = ((TimeStr.Len()<4)?"0":"") + TimeStr.Left(TimeStr.Len() - 2) + ":" + TimeStr.Right(2);
		if (!time.ParseTime(NewStr))
		{
			return(TimeStr);
		}
	}
	return time.Format("%H%M");
}
wxXmlNode* ObservationData::SaveXML()
{
	wxXmlNode *DataNode = new wxXmlNode(wxXML_ELEMENT_NODE,"ObservationData");
	//Header
	ADD_XML_FORMATTED_STRING_NODE(DataNode, Date)
	ADD_XML_FORMATTED_STRING_NODE(DataNode, Time)
	ADD_XML_STRING_NODE(DataNode, Observer)
	ADD_XML_ARRAY_NODE(DataNode, DayOfWeekStr, DayOfWeek)
	ADD_XML_ARRAY_NODE(DataNode, TimezoneStr, Timezone)
	ADD_XML_STRING_NODE(DataNode, Notes)
	//Current Values
	ADD_XML_FLOAT_NODE(DataNode, Temp)
	ADD_XML_FLOAT_NODE(DataNode, DewPoint)
	ADD_XML_FLOAT_NODE(DataNode, PresChg3hr)
	ADD_XML_FLOAT_NODE(DataNode, StationPres)
	ADD_XML_FLOAT_NODE(DataNode, SeaLvlPres)
	ADD_XML_FLOAT_NODE(DataNode, WindSpeed)
	ADD_XML_FLOAT_NODE(DataNode, WindDir)
	ADD_XML_INT_NODE(DataNode, CloudCover)
	ADD_XML_STRING_NODE(DataNode, CloudType)
	//24 Hr Summary
	ADD_XML_FLOAT_NODE(DataNode, MaxTemp_8to8)
	ADD_XML_FLOAT_NODE(DataNode, MaxTemp_12to12)
	ADD_XML_FLOAT_NODE(DataNode, MinTemp_8to8)
	ADD_XML_FLOAT_NODE(DataNode, MinTemp_12to12)
	ADD_XML_FLOAT_NODE(DataNode, HDD_8to8)
	ADD_XML_FLOAT_NODE(DataNode, HDD_12to12)
	ADD_XML_FLOAT_NODE(DataNode, SolarRad)
	ADD_XML_FLOAT_NODE(DataNode, Precip)
	ADD_XML_FLOAT_NODE(DataNode, SnowFall)
	ADD_XML_FLOAT_NODE(DataNode, SnowDepth)
	ADD_XML_FLOAT_NODE(DataNode, AvgWindSpeed)

	return(DataNode);
}
bool ObservationData::LoadXML(wxXmlNode* CurrentNode)
{
	if (!CurrentNode || CurrentNode->GetName() != "ObservationData"){return(false);}
	bool Result = true;
	for(wxXmlNode *Current = CurrentNode->GetChildren(); Current != NULL; Current = Current->GetNext())
	{
		CHECK_XML_STRING_NODE(Current, Date)
		else CHECK_XML_STRING_NODE(Current, Time)
		else CHECK_XML_STRING_NODE(Current, Observer)
		else CHECK_XML_ARRAY_NODE(Current, DayOfWeek, DayOfWeekStr, 7, DayOfWeek)
		else CHECK_XML_ARRAY_NODE(Current, Timezone, TimezoneStr, 2, Timezone)
		else CHECK_XML_STRING_NODE(Current, Notes)
		//Current Values
		else CHECK_XML_FLOAT_NODE(Current, Temp)
		else CHECK_XML_FLOAT_NODE(Current, DewPoint)
		else CHECK_XML_FLOAT_NODE(Current, PresChg3hr)
		else CHECK_XML_FLOAT_NODE(Current, StationPres)
		else CHECK_XML_FLOAT_NODE(Current, SeaLvlPres)
		else CHECK_XML_FLOAT_NODE(Current, WindSpeed)
		else CHECK_XML_FLOAT_NODE(Current, WindDir)
		else CHECK_XML_INT_NODE(Current, CloudCover)
		else CHECK_XML_STRING_NODE(Current, CloudType)
		//24 Hr Summary
		else CHECK_XML_FLOAT_NODE(Current, MaxTemp_8to8)
		else CHECK_XML_FLOAT_NODE(Current, MaxTemp_12to12)
		else CHECK_XML_FLOAT_NODE(Current, MinTemp_8to8)
		else CHECK_XML_FLOAT_NODE(Current, MinTemp_12to12)
		else CHECK_XML_FLOAT_NODE(Current, HDD_8to8)
		else CHECK_XML_FLOAT_NODE(Current, HDD_12to12)
		else CHECK_XML_FLOAT_NODE(Current, SolarRad)
		else CHECK_XML_FLOAT_NODE(Current, Precip)
		else CHECK_XML_FLOAT_NODE(Current, SnowFall)
		else CHECK_XML_FLOAT_NODE(Current, SnowDepth)
		else CHECK_XML_FLOAT_NODE(Current, AvgWindSpeed)
	}
	return(Result);
}
wxTextOutputStream& ObservationData::ExportCSV(wxTextOutputStream& strm)
{
	//Header
	WRITE_CSV_FORMATTED_STRING(strm, Date)
	WRITE_CSV_FORMATTED_STRING(strm, Time)
	WRITE_CSV_ARRAY(strm, DayOfWeekStr, DayOfWeek)
	WRITE_CSV_ARRAY(strm, TimezoneStr, Timezone)
	WRITE_CSV_STRING(strm, Observer)
	//Current Values
	WRITE_CSV_FLOAT(strm, Temp)
	WRITE_CSV_FLOAT(strm, DewPoint)
	WRITE_CSV_FLOAT(strm, PresChg3hr)
	WRITE_CSV_FLOAT(strm, StationPres)
	WRITE_CSV_FLOAT(strm, SeaLvlPres)
	WRITE_CSV_FLOAT(strm, WindSpeed)
	WRITE_CSV_FLOAT(strm, WindDir)
	WRITE_CSV_INT(strm, CloudCover)
	WRITE_CSV_STRING(strm, CloudType)
	//24 Hr Summary
	WRITE_CSV_FLOAT(strm, MaxTemp_8to8)
	WRITE_CSV_FLOAT(strm, MaxTemp_12to12)
	WRITE_CSV_FLOAT(strm, MinTemp_8to8)
	WRITE_CSV_FLOAT(strm, MinTemp_12to12)
	WRITE_CSV_FLOAT(strm, HDD_8to8)
	WRITE_CSV_FLOAT(strm, HDD_12to12)
	WRITE_CSV_FLOAT(strm, SolarRad)
	WRITE_CSV_FLOAT(strm, Precip)
	WRITE_CSV_FLOAT(strm, SnowFall)
	WRITE_CSV_FLOAT(strm, SnowDepth)
	WRITE_CSV_FLOAT(strm, AvgWindSpeed)

	WRITE_CSV_STRING(strm, Notes)
	return(strm);
}
wxTextOutputStream& ObservationData::ExportSCRNOBS(wxTextOutputStream& strm)
{
	wxRegEx re("[[:punct:][:space:]]+");
	if (!re.IsValid()){wxMessageBox("Bad RE");}
	wxString Observer = this->m_Observer.Upper(); re.Replace(&Observer,"");
	wxString CloudType = this->m_CloudType; re.Replace(&CloudType,"");

	//FORMAT(3I2,A3,I4,A3,A2,  4I3,2I2,  I6,I3,I6,I4,I2,   3I3,I2,A4,I2,14A4)
	//%2d %2d %2d %3s %4d %3s %2s
	strm << this->GetSCRNOBSFormattedDate();
	strm << DayOfWeekStr[this->m_DayOfWeek].Upper();
	strm << this->GetSCRNOBSFormattedTime();
	strm << TimezoneStr[this->m_Timezone].Upper();
	strm << wxString::Format("%2s",Observer.Mid(0,2));

	//%3d, %3d, %3d, %3d, %2d, %2d,
	strm << wxString::Format("%3.0f",this->m_Temp);
	strm << wxString::Format("%3.0f",this->m_MaxTemp_12to12);
	strm << wxString::Format("%3.0f",this->m_DewPoint);
	strm << wxString::Format("%3.0f",this->m_MinTemp_12to12);
	strm << wxString::Format("%2.0f",abs(this->m_PresChg3hr*10.0)); //multiply by 10 so 1.7 becomes 17 (no decimal)
	strm << wxString::Format("%2.0f",this->m_HDD_12to12);

	//%6d, %3d, %6d, %4d, %2d, 
	strm << wxString::Format("%6.0f",this->m_StationPres*100.0); //multiply by 100 so 1977.09 becomes 197709 (no decimal)
	strm << wxString::Format("%3.0f",this->m_SolarRad);
	strm << wxString::Format("%6.0f",this->m_SeaLvlPres*100.0); //multiply by 100 so 1977.09 becomes 197709 (no decimal)
	strm << wxString::Format("%4.0f",this->m_Precip*100.0); //multiply by 100 for same reason as others
	strm << wxString::Format("%2.0f",this->m_WindSpeed);

	//%3d, %3d, %3d, %2d, %4s, %2d, %56s
	strm << wxString::Format("%3.0f",this->m_SnowFall);
	strm << wxString::Format("%3.0f",this->m_WindDir);
	strm << wxString::Format("%3.0f",this->m_SnowDepth);
	strm << wxString::Format("%2d",this->m_CloudCover);
	strm << wxString::Format("%-4s",CloudType.Mid(0,4));
	strm << wxString::Format("%2.0f",this->m_AvgWindSpeed);
	strm << wxString::Format("%-56s",this->m_Notes.Mid(0,56));

//	strm << wxString::Format("%2d",this->m_MaxTemp_8to8);
//	strm << wxString::Format("%2d",this->m_MinTemp_8to8);
//	strm << wxString::Format("%2d",this->m_HDD_8to8);
	return(strm);
}

void ObservationDataIDHash::Destroy()
{
	for(ObservationDataIDHash::iterator it = this->begin(), end = this->end(); it != end; ++it)
	{
		wxDELETE(it->second);
	}
	this->clear();
}
ObservationDataIDHash ObservationData::sm_data;
bool ObservationData::sm_changed = true;


DECLARE_EXPOSURE_CHOICE_STATIC_TYPE(Timezone, 2,
	EST, "EST", "", 
	EDT, "EDT", "")
DECLARE_EXPOSURE_CHOICE_STATIC_TYPE(DayOfWeek, 7, 
	MON, "Monday", "",
	TUE, "Tuesday", "",
	WED, "Wednesday", "",
	THU, "Thursday", "",
	FRI, "Friday", "",
	SAT, "Saturday", "", 
	SUN, "Sunday", "")

BEGIN_EXPOSURE_TABLE(ObservationData, "One card of Meteorological Observation Data")
	
	EXPOSURE_GROUP()->Expand()
		EXPOSE_TEXT("Date","Month/Day/Yr.:", ObservationData::GetDate, ObservationData::SetDate, "The date of the observation")->AlignCenter()
		EXPOSE_CHOICE("DayOfWeek","Day of Week:",ObservationData::GetDayOfWeek, ObservationData::SetDayOfWeek, "The day of the week")->AlignCenter()
	EXPOSURE_END_GROUP()
	EXPOSURE_ROW()
	EXPOSURE_GROUP()->Expand()
		EXPOSE_TEXT("Time","Time of Obs.:",ObservationData::GetTime, ObservationData::SetTime, "The time of the observation")
		EXPOSE_CHOICE("Timezone","",ObservationData::GetTimezone,ObservationData::SetTimezone,"The timezone")
		EXPOSE_TEXT("Observer","Observer:",ObservationData::GetObserver,ObservationData::SetObserver,"The initials of the observer")
	EXPOSURE_END_GROUP()
	EXPOSURE_ROW()
	EXPOSURE_GROUP()
		EXPOSURE_GROUP("Current Values")
				EXPOSE_TEXT("Temp","Temp:",ObservationData::GetTemp,ObservationData::SetTemp,"The temperature (degrees F)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("DewPt","Dew Pt.:", ObservationData::GetDewPoint, ObservationData::SetDewPoint,"The dew point (degrees F)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("PresChg3hr","3 Hr. Pres Chg.:", ObservationData::GetPresChg3hr, ObservationData::SetPresChg3hr, "The 3 hour pressure change (mb)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("StationPres","Station Pres.:", ObservationData::GetStationPres, ObservationData::SetStationPres, "The station pressure (mb)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("SeaLevelPres","Sea-Level Pres.:", ObservationData::GetSeaLvlPres, ObservationData::SetSeaLvlPres, "Sea level pressure (mb)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("WindSpeed","Wind-speed:", ObservationData::GetWindSpeed, ObservationData::SetWindSpeed, "The wind speed (mph)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("WindDir", "Wind-dir.:",ObservationData::GetWindDir, ObservationData::SetWindDir,"Wind direction (azimuth)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("CloudAMT","Cloud AMT:",ObservationData::GetCloudCover, ObservationData::SetCloudCover, "Cloud cover (in eighths)")
				EXPOSE_TEXT("CloudType","Cloud Type:", ObservationData::GetCloudType, ObservationData::SetCloudType, "Cloud type")
		EXPOSURE_END_GROUP()
		EXPOSURE_GROUP("24-Hr. Summary")
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("MaxTemp8to8","Max. Temp.:",ObservationData::GetMaxTemp_8to8, ObservationData::SetMaxTemp_8to8, "Maximum temperature - 8 to 8 (degrees F)")
			//EXPOSURE_END_GROUP()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("MaxTemp12to12","|", ObservationData::GetMaxTemp_12to12, ObservationData::SetMaxTemp_12to12, "Maximum temperature - 12 to 12 (degrees F)")
			//EXPOSURE_END_GROUP()
			EXPOSURE_ROW()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("MinTemp8to8","Min. Temp.:",ObservationData::GetMinTemp_8to8, ObservationData::SetMinTemp_8to8, "Minimum temperature - 8 to 8 (degrees F)")
			//EXPOSURE_END_GROUP()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("MinTemp12to12","|", ObservationData::GetMinTemp_12to12, ObservationData::SetMinTemp_12to12, "Minimum temperature - 12 to 12 (degrees F)")
			//EXPOSURE_END_GROUP()
			EXPOSURE_ROW()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("HDD8to8","HDD:",ObservationData::GetHDD_8to8, ObservationData::SetHDD_8to8, "Heating degree days (8 to 8)")
			//EXPOSURE_END_GROUP()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("HDD12to12","|", ObservationData::GetHDD_12to12, ObservationData::SetHDD_12to12, "Heating degree days (12 to 12)")
			//EXPOSURE_END_GROUP()
			EXPOSURE_ROW()
			//EXPOSURE_GROUP()
				EXPOSE_TEXT("SolarRad","Solar Rad.:",ObservationData::GetSolarRad,ObservationData::SetSolarRad,"The solar radiation (cal/cm^2)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("Precip","Precip.:",ObservationData::GetPrecip,ObservationData::SetPrecip,"Precipitation (inches)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("SnowFall","Snow-fall:",ObservationData::GetSnowFall,ObservationData::SetSnowFall,"Snow fall (inches)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("SnowDepth","Snow-depth:",ObservationData::GetSnowDepth,ObservationData::SetSnowDepth,"Snow depth (inches)")
				EXPOSURE_ROW()
				EXPOSE_TEXT("AvgWindspeed","Avg. Windspeed:",ObservationData::GetAvgWindSpeed,ObservationData::SetAvgWindSpeed,"The average windspeed (mph)")
			//EXPOSURE_END_GROUP()
		EXPOSURE_END_GROUP()
	EXPOSURE_END_GROUP()
	EXPOSURE_ROW()
	EXPOSURE_GROUP("Notes")->Expand()
		EXPOSE_TEXT("Notes","",ObservationData::GetNotes,ObservationData::SetNotes,"Any extra notes")->Multiline()->Expand()
	EXPOSURE_END_GROUP()
END_EXPOSURE_TABLE()
